#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <ctype.h>
#include <sys/statvfs.h>

#include "disk.h"
#include "func.h"
#include "private_common.h"


struct diskdata
{
	char mount[MAX_LEN_NAME];
        char device[64];
        double total_size;
        double total_free;
        double total_used;
       	double thispct;
};
static struct diskdata *disk_data = NULL;
static uint8_t disk_num = 0;



static int remote_mount(const char *device, const char *type)
{
	/* From ME_REMOTE macro in mountlist.h:
	 *        A file system is `remote' if its Fs_name contains a `:'
	 *               or if (it is of type smbfs and its Fs_name starts with `//'). */
	return ((strchr(device,':') != 0)
			|| (!strcmp(type, "smbfs") && device[0]=='/' && device[1]=='/')
			|| (!strncmp(type, "nfs", 3)) || (!strcmp(type, "autofs"))
			|| (!strcmp(type,"gfs")) || (!strcmp(type,"none")) );
}

static int  disk_init(p_monitor_node *ml, const char* ignore)
{
	char *p;
	char *mount, *device, *type, *mode, *other;
	double total_size, total_free, thispct;
	char line[1024];
	FILE* disk = fopen("/proc/mounts","r");
	if (!disk) {
		return -1;
	}

	char _mount[250][MAX_LEN_NAME];
	char _device[250][64];
	int k = 0;
	char __mount[MAX_LEN_NAME];
	char __device[64];

	disk_num = 0;
	while (fgets(line, sizeof(line), disk) ) {
		p = skip_whitespace(line);
		if (startsWith(p, "/dev/") || startsWith(p, "/dev2/")) {
			device = p;
			mount = index(p, ' ');
			if (mount == NULL) continue;
			*mount++ = '\0';
			type = index(mount, ' ');
			if (type == NULL) continue;
			*type++ = '\0';
			mode = index(type, ' ');
			if (mode == NULL) continue;
			*mode++ = '\0';
			other = index(mode, ' ');
			if (other != NULL) *other = '\0';
			if (!strncmp(mode, "ro", 2)) continue;
			if (remote_mount(device, type)) continue;
			if (ignore) {
				snprintf(__mount, MAX_LEN_NAME-1, "%s,", mount);
				snprintf(__device, 63, "%s,", device);
				if (strstr(ignore, __mount) || strstr(ignore, __device)) {
					continue;
				}
			}
			snprintf(_mount[k], MAX_LEN_NAME-1, "%s", mount);
			snprintf(_device[k], 63, "%s", device);
			k++;
		}
	}
	fclose(disk);
	if (k > 250) k = 250;
	disk_data = (struct diskdata*)malloc(sizeof(struct diskdata)*k);
	for (int i = 0;i < k; i++) {
		snprintf((disk_data+i)->mount, MAX_LEN_NAME-1, "%s", _mount[i]);
		snprintf((disk_data+i)->device, 63, "%s", _device[i]);
	}
	disk_num = k;
	return 0;
}

static void disk_update(p_monitor_node *ml)
{
	struct statvfs svfs;
	double blocksize;
	double free;
	double size;
	double avail;
	/* The percent used: used/total * 100 */
	for (int i= 0;i < disk_num; i++) {
		(disk_data+i)->thispct = 0;
		if (statvfs((disk_data+i)->mount, &svfs)) {
			/* Ignore funky devices... */
			continue;
		}

		avail = svfs.f_bavail;
		free  = svfs.f_bfree;
		size  = svfs.f_blocks;
		blocksize = svfs.f_bsize;
		/* Keep running sum of total used, free local disk space. */
		(disk_data+i)->total_size = size  * blocksize / COST('G');
		(disk_data+i)->total_free = avail * blocksize / COST('G');
		(disk_data+i)->total_used = (size - free) * blocksize / COST('G');
		/* The percentage of space used on this partition. */
		(disk_data+i)->thispct = size ? ((size - free) / (size - free + avail)) * 100 : 0.0;
		(disk_data+i)->thispct = (disk_data+i)->thispct < 0.01 ? 0.01 : (disk_data+i)->thispct;

		D_DECIMAL((disk_data+i)->total_size, 2);
		D_DECIMAL((disk_data+i)->total_free, 2);
		D_DECIMAL((disk_data+i)->total_used, 2);
		D_DECIMAL((disk_data+i)->thispct, 2);
	}
}

static void disk_to_json(cJSON* json)
{
	cJSON *item, *node;
	for (int i = 0;i < disk_num; i++) {
		item = cJSON_GetObjectItemCaseSensitive(json, (disk_data+i)->device);
		if (item) {
			node = cJSON_GetObjectItem(json, (disk_data+i)->device);
			cJSON_SetValuestring(cJSON_GetObjectItem(node, "mount"), (disk_data+i)->mount);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "total"), (disk_data+i)->total_size);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "used"), (disk_data+i)->total_used);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "free"), (disk_data+i)->total_free);
			cJSON_SetNumberValue(cJSON_GetObjectItem(node, "pct"), (disk_data+i)->thispct);
		} else {
			node = cJSON_CreateObject();
			cJSON_AddStringToObject(node, "mount", (disk_data+i)->mount);
			cJSON_AddNumberToObject(node, "total", (disk_data+i)->total_size);
			cJSON_AddNumberToObject(node, "used", (disk_data+i)->total_used);
			cJSON_AddNumberToObject(node, "free", (disk_data+i)->total_free);
			cJSON_AddNumberToObject(node, "pct",   (disk_data+i)->thispct);
			cJSON_AddItemToObject(json, (disk_data+i)->device, node);
		}
	}
}

static void disk_clean() {
	disk_num = 0;
	if (disk_data)
		free(disk_data);
	disk_data = NULL;
}

static uint32_t disk_to_text(char* buffer, uint32_t size)
{
        char line[5][64];
        uint32_t k1 = 0, k2 = size;
        int r;
	for (int i = 0;i < disk_num; i++) {
                sprintf(line[0], "%s,mount,%s", (disk_data+i)->device, (disk_data+i)->mount);
                sprintf(line[1], "%s,total,%.3lf", (disk_data+i)->device, (disk_data+i)->total_size);
                sprintf(line[2], "%s,used,%.3lf", (disk_data+i)->device, (disk_data+i)->total_used);
                sprintf(line[3], "%s,free,%.3lf", (disk_data+i)->device, (disk_data+i)->total_free);
                sprintf(line[4], "%s,pct,%.2f", (disk_data+i)->device, (disk_data+i)->thispct);
                r = snprintf(buffer+k1, k2, "%s\n%s\n%s\n%s\n%s\n", line[0], line[1], line[2], line[3], line[4]);
                if (r <= 0) return k1;
                k1 += r;
                if (k1 >= size) break;
		k2 -= r;
	}
	return k1;
}

void disk_task_init(ptask t, char* _ignore, uint16_t _uf)
{
        t->init    = disk_init;
        t->update  = disk_update;
        t->to_json = disk_to_json;
        t->clean   = disk_clean;
        t->json    = NULL;
        t->ignore  = _ignore;
        t->uf      = _uf;
        t->cu      = 0;
	t->to_text = disk_to_text;
}


